package dao

import (
	"context"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type InterActiveDAO interface {
	IncrReadCnt(ctx context.Context, biz string, bizId int64) error
	InsertLikeInfo(ctx context.Context, biz string, bizId int64, uId int64) error
	DeleteLikeInfo(ctx context.Context, biz string, bizId int64, uId int64) error
	InsertCollectionItem(ctx context.Context, biz string, id int64, uId int64) error
	Get(ctx context.Context, biz string, id int64) (InterActive, error)
	GetLikeInfo(ctx context.Context, biz string, id int64, uId int64) (UserLikeBiz, error)
	GetCollectInfo(ctx context.Context, biz string, id int64, uId int64) (UserCollectBiz, error)
	BatchIncrReadCnt(ctx context.Context, bizs []string, ids []int64) error
	GetByIds(ctx context.Context, biz string, ids []int64) ([]InterActive, error)
}

type GORMInterActiveDAO struct {
	db *gorm.DB
}

func (g *GORMInterActiveDAO) GetByIds(ctx context.Context, biz string, ids []int64) ([]InterActive, error) {
	var res []InterActive
	err := g.db.WithContext(ctx).Model(&InterActive{}).
		Where("biz = ? AND biz_id IN ?", biz, ids).
		Find(&res).Error
	return res, err
}

func (g *GORMInterActiveDAO) GetLikeInfo(ctx context.Context, biz string, id int64, uId int64) (UserLikeBiz, error) {
	var ul UserLikeBiz
	err := g.db.WithContext(ctx).Model(&UserLikeBiz{}).
		Where("biz_id =? AND biz = ? AND uid = ?", id, biz, uId).First(&ul).Error
	if err != nil {
		//这里会有ErrRecondNotFound错误
		return UserLikeBiz{}, err
	}

	return ul, nil
}

func (g *GORMInterActiveDAO) GetCollectInfo(ctx context.Context, biz string, id int64, uId int64) (UserCollectBiz, error) {
	var ul UserCollectBiz
	err := g.db.WithContext(ctx).Model(&UserCollectBiz{}).
		Where("biz_id =? AND biz = ? AND uid = ?", id, biz, uId).First(&ul).Error
	if err != nil {
		//这里会有ErrRecondNotFound错误
		return UserCollectBiz{}, err
	}

	return ul, nil
}

func (g *GORMInterActiveDAO) Get(ctx context.Context, biz string, id int64) (InterActive, error) {
	var ie InterActive
	err := g.db.WithContext(ctx).Model(&InterActive{}).
		Where("biz_id =? AND biz = ?", id, biz).First(&ie).Error
	if err != nil {
		//这里会有ErrRecondNotFound错误
		return InterActive{}, err
	}

	return ie, nil
}

func (g *GORMInterActiveDAO) InsertCollectionItem(ctx context.Context, biz string, id int64, uId int64) error {
	//增加一个收藏项，更新收藏数量
	now := time.Now().UnixMilli()
	err := g.db.Transaction(func(tx *gorm.DB) error {
		err := g.db.WithContext(ctx).Create(&UserCollectBiz{
			BizId:  id,
			Biz:    biz,
			Uid:    uId,
			Status: 1,
			Ctime:  now,
			Utime:  now,
		}).Error
		if err != nil {
			return err
		}

		//这里要upsert
		err = g.db.WithContext(ctx).Clauses(clause.OnConflict{DoUpdates: clause.Assignments(map[string]any{
			"collect_cnt": gorm.Expr("collect_cnt + 1"),
			"utime":       now,
		})}).
			Create(&InterActive{
				BizId:      id,
				Biz:        biz,
				ReadCnt:    0,
				LikeCnt:    0,
				CollectCnt: 1,
			}).Error
		return err
	})
	return err
}

func NewGORMInterActiveDAO(db *gorm.DB) InterActiveDAO {
	return &GORMInterActiveDAO{
		db: db,
	}
}

func (g *GORMInterActiveDAO) InsertLikeInfo(ctx context.Context, biz string, bizId int64, uId int64) error {
	now := time.Now().UnixMilli()
	err := g.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		//更新用户点赞记录 和点赞数量
		//这里的用户点赞记录必须使用upsert语句，因为一定会有第一次插入的场景。而用户取消点赞哪里就不需要upsert了
		err := tx.Clauses(clause.OnConflict{DoUpdates: clause.Assignments(map[string]any{
			"status": 1,
			"utime":  now,
		})}).
			WithContext(ctx).Create(&UserLikeBiz{
			BizId:  bizId,
			Biz:    biz,
			Uid:    uId,
			Status: 1,
			Ctime:  now,
			Utime:  now,
		}).Error
		if err != nil {
			return err
		}

		//这个地方是取消点赞，我们需不需要UPSERT呢？
		err = tx.Clauses(clause.OnConflict{DoUpdates: clause.Assignments(map[string]any{
			"like_cnt": gorm.Expr("like_cnt + 1"),
			"utime":    now,
		})}).
			WithContext(ctx).Create(&InterActive{
			BizId:   bizId,
			Biz:     biz,
			LikeCnt: 1,
			Ctime:   now,
			Utime:   now,
		}).Error

		return err
	})

	return err
}

func (g *GORMInterActiveDAO) DeleteLikeInfo(ctx context.Context, biz string, bizId int64, uId int64) error {
	now := time.Now().UnixMilli()
	err := g.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Model(&UserLikeBiz{}).Where("biz_id =? AND biz = ? AND uid = ?", bizId, biz, uId).
			Updates(map[string]any{
				"status": 0,
				"utime":  now,
			}).Error
		if err != nil {
			return err
		}

		err = tx.Clauses(clause.OnConflict{DoUpdates: clause.Assignments(map[string]any{
			"like_cnt": gorm.Expr("like_cnt - 1"),
			"utime":    now,
		})}).
			WithContext(ctx).Create(&InterActive{
			BizId:   bizId,
			Biz:     biz,
			LikeCnt: 1,
			Ctime:   now,
			Utime:   now,
		}).Error

		return err
	})

	return err
}

func (g *GORMInterActiveDAO) IncrReadCnt(ctx context.Context, biz string, bizId int64) error {
	now := time.Now().UnixMilli()
	//这里做一个upsert的操作，我们是更新阅读次数，当这个列不存在时就插入
	err := g.db.Clauses(clause.OnConflict{
		DoUpdates: clause.Assignments(map[string]any{
			"read_cnt": gorm.Expr("read_cnt + 1"),
			"utime":    now,
		}),
	}).Create(&InterActive{
		BizId:   bizId,
		Biz:     biz,
		ReadCnt: 1,
		Ctime:   now,
		Utime:   now,
	}).Error

	return err
}

func (g *GORMInterActiveDAO) BatchIncrReadCnt(ctx context.Context, bizs []string, ids []int64) error {
	err := g.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		IncrDAO := NewGORMInterActiveDAO(tx)
		for i := 0; i < len(bizs); i++ {
			er := IncrDAO.IncrReadCnt(ctx, bizs[i], ids[i])
			if er != nil {
				return er
			}
		}
		return nil
	})
	return err
}

type InterActive struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`

	//这里给bizId和biz建立联合唯一索引, bizId和biz就可以确定一个唯一的资源。这里把bizId放在前面，是因为这个字段的区分度大一些。IO效率高
	BizId int64  `gorm:"uniqueIndex:biz_type_id"`
	Biz   string `gorm:"uniqueIndex:biz_type_id; type:varchar(128)"`

	//阅读，点赞，收藏个数
	ReadCnt    int64
	LikeCnt    int64
	CollectCnt int64

	Ctime int64
	Utime int64
}

type UserLikeBiz struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`

	//这里给bizId和biz建立联合唯一索引, bizId和biz就可以确定一个唯一的资源。这里把bizId放在前面，是因为这个字段的区分度大一些。IO效率高
	BizId int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	Biz   string `gorm:"uniqueIndex:uid_biz_type_id; type:varchar(128)"`
	Uid   int64  `gorm:"uniqueIndex:uid_biz_type_id"`

	//使用软删除
	//0-代表无效 1-代表有效
	Status uint8

	Ctime int64
	Utime int64
}

type UserCollectBiz struct {
	Id int64 `gorm:"primaryKey,autoIncrement"`

	//这里给bizId和biz建立联合唯一索引, bizId和biz就可以确定一个唯一的资源。这里把bizId放在前面，是因为这个字段的区分度大一些。IO效率高
	BizId int64  `gorm:"uniqueIndex:uid_biz_type_id"`
	Biz   string `gorm:"uniqueIndex:uid_biz_type_id; type:varchar(128)"`
	Uid   int64  `gorm:"uniqueIndex:uid_biz_type_id"`

	//使用软删除
	//0-代表无效 1-代表有效
	Status uint8

	Ctime int64
	Utime int64
}
